package hu.mapro.rollerx.core;

import java.util.ArrayList;
import java.util.Iterator;

import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Contact;
import com.badlogic.gdx.physics.box2d.ContactImpulse;
import com.badlogic.gdx.physics.box2d.ContactListener;
import com.badlogic.gdx.physics.box2d.Fixture;
import com.badlogic.gdx.physics.box2d.Manifold;
import com.badlogic.gdx.physics.box2d.World;

public class RollerWorld {

	final World world;
	
	final Car car;
	final Ground ground;
	final Cargo cargo;
	
	public RollerWorld(
			Car car,
			Ground ground,
			Cargo cargo
	) {
		this.car = car;
		this.ground = ground;
		this.cargo = cargo;

		world = new World(defaultGravity, false);
		
		
		ground.init(this, car.getWidth(), car.getHeigth());
		Vector2 carPosition = ground.getCarPosition(car.getWidth(), car.getHeigth());
		car.init(this, carPosition);
		cargo.init(this, carPosition, car.getPlatformHeight());
		this.init();
	}

	// collision bits
	
	public static final int COLLISION_GROUND = 1 << 0;
	public static final int COLLISION_CAR = 1 << 1;
	public static final int COLLISION_CARGO = 1 << 2;
	public static final int COLLISION_BROKEN_CARGO = 1 << 3;
	public static final int COLLISION_CARGO_GROUND_SENSOR = 1 << 4;
	
	// Physics
	
	private static final Vector2 defaultGravity = new Vector2(0, -10);
	
	static final float BOX_STEP=1/300f;
	static final int  BOX_VELOCITY_ITERATIONS=6;
	static final int BOX_POSITION_ITERATIONS=3;	

	private static final float MAX_STEP = 0.1f;
	
	float accumulator;
	float elapsedTime;

	private ContactListener contactListener;
	
	
	


	public void step(float delta) {
		if (delta > MAX_STEP)
			delta = MAX_STEP;
		
		elapsedTime += delta;

		Iterator<ElapsedHandler> elapsedIterator = elapsedHandlers.iterator();
		while (elapsedIterator.hasNext()) {
			ElapsedHandler elapsedHandler = elapsedIterator.next();
			if (elapsedHandler.elapsed()) {
				elapsedIterator.remove();
			}
		}

		cargo.step();
		
		accumulator += delta;
		while (accumulator > BOX_STEP) {

			world.step(BOX_STEP, BOX_VELOCITY_ITERATIONS, BOX_POSITION_ITERATIONS);
			accumulator -= BOX_STEP;
		}
		
	}
	

	private void init() {
		
		contactListener = new ContactListener() {
			@Override
			public void preSolve(Contact contact, Manifold oldManifold) {
			}
			
			@Override
			public void postSolve(Contact contact, ContactImpulse impulse) {
			}
			
			@Override
			public void endContact(Contact contact) {
			}
			
			@Override
			public void beginContact(Contact contact) {
				
				UserData userDataA = (UserData) contact.getFixtureA().getBody().getUserData();
				UserData userDataB = (UserData) contact.getFixtureB().getBody().getUserData();

				if (userDataA!=null && userDataB!=null) {
					userDataA.contact(userDataB, contact, ContactMember.B);
				}
				
			}
		};
		
		
		world.setContactListener(contactListener);
		
	}

	public Vector2 rotate(Vector2 v, float degrees) {
		return rotateLocal(new Vector2(v), degrees); 
	}
	
	public Vector2 rotateLocal (Vector2 v, float degrees) {
		float rad = degrees * MathUtils.degreesToRadians;
		float cos = (float)Math.cos(rad);
		float sin = (float)Math.sin(rad);

		float newX = v.x * cos - v.y * sin;
		float newY = v.x * sin + v.y * cos;

		v.x = newX;
		v.y = newY;

		return v;
	}

	public void setGravityAngle(float degrees) {
		world.setGravity(rotate(defaultGravity, degrees));
	}

	public void dispose() {
		world.dispose();
		car.dispose();
		ground.dispose();
		cargo.dispose();
	}
	
	interface UserData {

		void contact(UserData userData, Contact contact,
				ContactMember contactedMember);

		void contactCargo(Contact contact, ContactMember a);

		void contactGround(Contact contact, ContactMember a);
		
	}
	
	abstract class ElapsedHandler {
		
		float bornTime;
		
		public ElapsedHandler() {
			bornTime = elapsedTime;
		}
		
		float age() {
			return elapsedTime - bornTime;
		}
		
		abstract boolean elapsed();
	}
	
	ArrayList<ElapsedHandler> elapsedHandlers = new ArrayList<RollerWorld.ElapsedHandler>();
	

	enum ContactMember {
		A {
			@Override
			Fixture thisFixture(Contact contact) {
				return contact.getFixtureA();
			}

			@Override
			Fixture otherFixture(Contact contact) {
				return contact.getFixtureB();
			}
		},
		B {
			@Override
			Fixture thisFixture(Contact contact) {
				return contact.getFixtureB();
			}
			
			@Override
			Fixture otherFixture(Contact contact) {
				return contact.getFixtureA();
			}
		},
		;
		abstract Fixture thisFixture(Contact contact);
		abstract Fixture otherFixture(Contact contact);
	}

	protected void cargoHitGround(Fixture cargoFixture) {
		cargo.cargoHitGround(cargoFixture);
	}

	Vector2 getView() {
		return car.getPosition();
		
	}
	
}
